﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Xml.Serialization;

namespace Dotnet.Utils.Utility.Serialize
{
    public class SerializeUtil
    {
        #region 深拷贝
        
        /// <summary>
        /// 深拷贝
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source">需要拷贝数据</param>
        /// <returns></returns>
        public T Clone<T>(T source)
        {
            if (!typeof(T).IsSerializable)
                throw new ArgumentException("The type must be serializable.", "source");

            if (source == null)
                return default;

            IFormatter formatter = new BinaryFormatter();
            using (Stream stream = new MemoryStream())
            {
                formatter.Serialize(stream, source);
                stream.Seek(0, SeekOrigin.Begin);
                return (T)formatter.Deserialize(stream);
            }
        }

        /// <summary>
        /// 深拷贝2
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj">需要拷贝数据</param>
        /// <returns></returns>
        public T Clone2<T>(T obj)
        {
            if (!typeof(T).IsSerializable)
                throw new ArgumentException("The type must be serializable.", "source");

            if (ReferenceEquals(obj, null))
                return default;
            //创建内存流
            MemoryStream ms = new MemoryStream();
            //以二进制格式进行序列化
            BinaryFormatter bf = new BinaryFormatter();
            bf.Serialize(ms, obj);
            //反序列化当前实例到一个object
            ms.Seek(0, 0);
            T res = (T)bf.Deserialize(ms);
            //关闭内存流
            ms.Close();
            return res;
        }

        #endregion


        internal static BinaryFormatter BinaryFormat
        {
            get
            {
                if (binaryFormat == null)
                {
                    binaryFormat = new BinaryFormatter();
                }
                return binaryFormat;
            }
        }

        public static void XmlSerialize(Type type, object obj, Stream outputStream)
        {
            new XmlSerializer(type).Serialize(outputStream, obj);
        }

        public static object XmlDeserialize(Type type, Stream inputStream)
        {
            return new XmlSerializer(type).Deserialize(inputStream);
        }

        public static void BinarySerialize(object obj, Stream outputStream)
        {
            BinaryFormat.Serialize(outputStream, obj);
        }

        public static object BinaryDeserialize(Stream inputStream)
        {
            return BinaryFormat.Deserialize(inputStream);
        }

        public static object CloneObject(object ob)
        {
            object result;
            using (MemoryStream memoryStream = new MemoryStream())
            {
                BinarySerialize(ob, memoryStream);
                result = BinaryDeserialize(memoryStream);
            }
            return result;
        }

        public static byte[] BinarySerialize(object obj)
        {
            byte[] result;
            using (MemoryStream memoryStream = new MemoryStream())
            {
                IFormatter formatter = new BinaryFormatter();
                formatter.Serialize(memoryStream, obj);
                byte[] array = memoryStream.ToArray();
                result = array;
            }
            return result;
        }

        public static object BinaryDeserialize(byte[] bytes)
        {
            object result;
            using (MemoryStream memoryStream = new MemoryStream(bytes))
            {
                IFormatter formatter = new BinaryFormatter();
                result = formatter.Deserialize(memoryStream);
            }
            return result;
        }

        public static Dictionary<string, object> SerializeDict(object busiObject)
        {
            Dictionary<string, object> dictionary = new Dictionary<string, object>();
            Type type = busiObject.GetType();
            PropertyInfo[] properties = type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
            foreach (PropertyInfo propertyInfo in properties)
            {
                object obj = propertyInfo.GetValue(busiObject, null);
                if (propertyInfo.PropertyType.IsEnum)
                {
                    obj = (int)obj;
                }
                dictionary.Add(propertyInfo.Name, obj);
            }
            return SerializeDict(busiObject, type.BaseType, dictionary);
        }

        private static Dictionary<string, object> SerializeDict(object busiObject, Type t, Dictionary<string, object> dict)
        {
            Dictionary<string, object> result;
            if (t == typeof(object))
            {
                result = dict;
            }
            else
            {
                PropertyInfo[] properties = t.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
                foreach (PropertyInfo propertyInfo in properties)
                {
                    object obj = propertyInfo.GetValue(busiObject, null);
                    if (propertyInfo.PropertyType.IsEnum)
                    {
                        obj = (int)obj;
                    }
                    dict.Add(propertyInfo.Name, obj);
                }
                dict = SerializeDict(busiObject, t.BaseType, dict);
                result = dict;
            }
            return result;
        }

        public static T DeSerializeDict<T>(Dictionary<string, object> data) where T : new()
        {
            T t = default(T) == null ? Activator.CreateInstance<T>() : default;
            Type type = t.GetType();
            foreach (KeyValuePair<string, object> keyValuePair in data)
            {
                if (keyValuePair.Value != null)
                {
                    PropertyInfo propertyInfo = type.GetProperty(keyValuePair.Key, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
                    if (propertyInfo == null)
                    {
                        propertyInfo = GetBasePropertyInfo(keyValuePair.Key, type);
                        if (propertyInfo == null)
                        {
                            continue;
                        }
                    }
                    if (propertyInfo.PropertyType.IsEnum)
                    {
                        object value = Enum.Parse(propertyInfo.PropertyType, keyValuePair.Value.ToString());
                        propertyInfo.SetValue(t, value, null);
                    }
                    else if (propertyInfo.PropertyType != keyValuePair.Value.GetType())
                    {
                        if (propertyInfo.PropertyType.Name == "Int32")
                        {
                            propertyInfo.SetValue(t, Convert.ToInt32(keyValuePair.Value), null);
                        }
                        else if (propertyInfo.PropertyType.Name == "Decimal")
                        {
                            propertyInfo.SetValue(t, Convert.ToDecimal(keyValuePair.Value), null);
                        }
                        else if (propertyInfo.PropertyType.Name == "Double")
                        {
                            propertyInfo.SetValue(t, Convert.ToDouble(keyValuePair.Value), null);
                        }
                        else if (propertyInfo.PropertyType.Name == "DateTime")
                        {
                            propertyInfo.SetValue(t, Convert.ToDateTime(keyValuePair.Value), null);
                        }
                        else if (propertyInfo.PropertyType.Name == "Int64")
                        {
                            propertyInfo.SetValue(t, Convert.ToInt64(keyValuePair.Value), null);
                        }
                        else if (propertyInfo.PropertyType.Name == "Int16")
                        {
                            propertyInfo.SetValue(t, Convert.ToInt16(keyValuePair.Value), null);
                        }
                        else if (propertyInfo.PropertyType.Name == "Float")
                        {
                            propertyInfo.SetValue(t, Convert.ToSingle(keyValuePair.Value), null);
                        }
                    }
                    else
                    {
                        propertyInfo.SetValue(t, keyValuePair.Value, null);
                    }
                }
            }
            return t;
        }

        public static object DeSerializeDict(Type t, Dictionary<string, object> data)
        {
            object obj = Activator.CreateInstance(t);
            foreach (KeyValuePair<string, object> keyValuePair in data)
            {
                if (keyValuePair.Value != null)
                {
                    PropertyInfo propertyInfo = t.GetProperty(keyValuePair.Key, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
                    if (propertyInfo == null)
                    {
                        propertyInfo = GetBasePropertyInfo(keyValuePair.Key, t);
                        if (propertyInfo == null)
                        {
                            continue;
                        }
                    }
                    if (propertyInfo.PropertyType.IsEnum)
                    {
                        object value = Enum.Parse(propertyInfo.PropertyType, keyValuePair.Value.ToString());
                        propertyInfo.SetValue(obj, value, null);
                    }
                    else if (propertyInfo.PropertyType != keyValuePair.Value.GetType())
                    {
                        if (propertyInfo.PropertyType.Name == "Int32")
                        {
                            propertyInfo.SetValue(obj, Convert.ToInt32(keyValuePair.Value), null);
                        }
                        else if (propertyInfo.PropertyType.Name == "Decimal")
                        {
                            propertyInfo.SetValue(obj, Convert.ToDecimal(keyValuePair.Value), null);
                        }
                        else if (propertyInfo.PropertyType.Name == "Double")
                        {
                            propertyInfo.SetValue(obj, Convert.ToDouble(keyValuePair.Value), null);
                        }
                        else if (propertyInfo.PropertyType.Name == "DateTime")
                        {
                            propertyInfo.SetValue(obj, Convert.ToDateTime(keyValuePair.Value), null);
                        }
                        else if (propertyInfo.PropertyType.Name == "Int64")
                        {
                            propertyInfo.SetValue(obj, Convert.ToInt64(keyValuePair.Value), null);
                        }
                        else if (propertyInfo.PropertyType.Name == "Int16")
                        {
                            propertyInfo.SetValue(obj, Convert.ToInt16(keyValuePair.Value), null);
                        }
                        else if (propertyInfo.PropertyType.Name == "Float")
                        {
                            propertyInfo.SetValue(obj, Convert.ToSingle(keyValuePair.Value), null);
                        }
                    }
                    else
                    {
                        propertyInfo.SetValue(obj, keyValuePair.Value, null);
                    }
                }
            }
            return obj;
        }

        private static PropertyInfo GetBasePropertyInfo(string name, Type t)
        {
            PropertyInfo result;
            if (t.BaseType == typeof(object))
            {
                result = null;
            }
            else
            {
                PropertyInfo propertyInfo = t.BaseType.GetProperty(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
                if (propertyInfo == null)
                {
                    propertyInfo = GetBasePropertyInfo(name, t.BaseType);
                }
                result = propertyInfo;
            }
            return result;
        }

        private static volatile BinaryFormatter binaryFormat;
    }
}